package org.test4j.integration;

import org.test4j.Context;
import org.test4j.Logger;
import org.test4j.module.Test4JListener;
import org.test4j.module.inject.proxy.InjectHelper;

import java.lang.reflect.Method;
import java.util.*;

public final class ListenerFactory {
    public static final ListenerFactory factory = new ListenerFactory();

    private List<Test4JListener> listeners = new ArrayList<>();

    private List<Test4JListener> reverseListeners = new ArrayList<>();
    /**
     * 是否已经执行过BeforeMethod
     */
    private ThreadLocal<Boolean> doneBeforeMethod = new ThreadLocal<>();

    private ThreadLocal<Boolean> needAfterMethod = new ThreadLocal<>();

    private static ThreadLocal<Test4JListener> testListener = new ThreadLocal<>();

    public static void setTestListener(Test4JListener listener) {
        testListener.set(listener);
    }

    private ListenerFactory() {
        ServiceLoader<Test4JListener> loaders = ServiceLoader.load(Test4JListener.class);
        for (Test4JListener loader : loaders) {
            if (loader.init()) {
                this.listeners.add(loader);
            } else {
                Logger.warn("find Test4JListener:" + loader.name() + ", but not init.");
            }
        }
        this.listeners.sort(Comparator.comparing(Test4JListener::order));
        this.reverseListeners = new ArrayList<>(this.listeners);
        Collections.reverse(this.reverseListeners);
    }

    public static void beforeAll(Class testClass) {
        if (testClass == null || factory.isAssignableFromLast(testClass)) {
            return;
        }
        /**
         * 如果上个测试类没有执行afterAll()
         */
        afterAll();

        Class aClass = testClass.isSynthetic() ? testClass.getSuperclass() : testClass;
        Context.currTestClass(aClass);
        factory.listeners.forEach(item -> item.beforeAll(aClass));
        if (testListener.get() != null) {
            testListener.get().beforeAll(aClass);
        }
    }

    public static void beforeMethod(Object target) {
        if (factory.needAfterMethod != null) {
            /**
             * 如果上个测试没有执行过afterMethod方法
             */
            afterMethod();
        }
        if (factory.doneBeforeMethod.get() == null) {
            beforeAll(target.getClass());

            Context.currTestObject(target);

            InjectHelper.injectIntoTestedObject(target);

            factory.listeners.forEach(item -> item.beforeMethod(target));
            if (testListener.get() != null) {
                testListener.get().beforeMethod(target);
            }
        }
        factory.doneBeforeMethod.set(true);
    }

    public static void beforeExecute(Object target, Method method) {
        /**
         * 如果执行测试前没有执行过beforeMethod()
         */
        beforeMethod(target);
        prepareNextTest();
        Context.currTestMethod(method);
        factory.listeners.forEach(item -> item.beforeExecute(target, method));
        if (testListener.get() != null) {
            testListener.get().beforeExecute(target, method);
        }
    }

    public static void afterExecute(Object target, Method method, Throwable e) {
        factory.reverseListeners.forEach(item -> item.afterExecute(target, method, e));
        if (testListener.get() != null) {
            testListener.get().afterExecute(target, method, e);
        }
        prepareNextTest();
    }

    private static void prepareNextTest() {
        factory.doneBeforeMethod.set(null);
        factory.needAfterMethod.set(true);
    }

    public static void afterMethod() {
        if (factory.needAfterMethod.get() != null) {
            factory.reverseListeners.forEach(item -> item.afterMethod());
            if (testListener.get() != null) {
                testListener.get().afterMethod();
            }
        }
        factory.needAfterMethod.set(null);
        Context.currTestMethod(null);
    }

    public static void afterAll() {
        afterMethod();
        if (Context.currTestClass() != null) {
            factory.reverseListeners.forEach(item -> item.afterAll());
            if (testListener.get() != null) {
                testListener.get().afterAll();
            }
        }
        Context.currTestClass(null);
        Context.currTestObject(null);
    }

    private boolean isAssignableFromLast(Class aClass) {
        Class currClass = Context.currTestClass();
        Class testClass = aClass.isSynthetic() ? aClass.getSuperclass() : aClass;
        if (currClass == null) {
            return false;
        } else {
            return currClass == testClass || currClass.isAssignableFrom(testClass);
        }
    }
}